Skip to content

fix(files): authorize downloads for wrapper apps via signed tokens - #528

Closed
xiaojueshi wants to merge 1 commit into
agegr:mainfrom
xiaojueshi:fix/signed-download-token
Closed

fix(files): authorize downloads for wrapper apps via signed tokens#528
xiaojueshi wants to merge 1 commit into
agegr:mainfrom
xiaojueshi:fix/signed-download-token

Conversation

@xiaojueshi

Copy link
Copy Markdown
Contributor

目的

Pi Web 可以开启 PI_WEB_PASSWORD(HTTP Basic 认证)。当页面被桌面封装应用
(如 Pake)包裹时,文件浏览器的下载会持续报
401 Unauthorized:封装应用会用自己的 HTTP 客户端重放下载链接,而该客户端
不携带 webview 已缓存的 Basic 凭据。

实现方式

用一段绑定文件路径、短时有效的签名令牌,替代缺失的 Basic 凭据,仅作用于
文件下载这一个请求:

  • app/api/auth/download-token(新增):在通过常规的 allowed-roots 校验
    (并对符号链接二次校验)后,为文件签发 5 分钟的 HMAC 签名令牌。端点本身
    仍受 Basic 认证保护。
  • proxy.ts:匹配 /api/files/**?type=download 且携带有效令牌的请求跳过
    Basic 检查;令牌无效或过期则显式返回 403。其余所有 /api/* 请求保持
    密码保护不变。
  • components/FileExplorer.tsx:悬停(hover)时预取令牌并写入下载链接的
    href——因为封装应用在 capture 阶段拦截点击,任何 React 点击处理器都来不及
    再取令牌。仅对封装应用会接管的扩展名附加可读文件名(download 属性)。
  • lib/download-auth.ts / lib/pake-download-extensions.ts(新增)及单元
    测试:覆盖签名/校验、过期、防篡改,以及镜像封装应用可下载扩展名清单的边界。

带来的变化

  • 开启 PI_WEB_PASSWORD 时,封装应用内下载恢复正常。
  • 非封装(浏览器)下载行为完全不变,含 Content-Disposition 文件名。
  • 安全性:令牌绑定文件路径、5 分钟过期,且仅对 type=download 形态生效,
    不能用于其他 API。签名密钥为进程内随机值(不落盘),除非显式设置
    PI_WEB_DOWNLOAD_SECRET

测试

  • 单元测试:lib/download-auth.test.mjs(7 项)、lib/pake-download-extensions.test.mjs(4 项)。
  • 集成验证(curl):携带令牌下载返回 200 且字节数完整;缺失/无效/过期/
    篡改路径的令牌被拒绝(401/403);Basic 认证下载回归通过。
  • 已在 Pake 封装环境实测:docx/xlsx 下载成功且文件名为可读中文。

Wrapper apps (e.g. Pake) replay file-download links through their own HTTP
client, which does not carry the page's Basic credentials, so every download
behind PI_WEB_PASSWORD fails with 401. Add a short-lived signed download token
(bound to the path, 5-min TTL) issued by a new endpoint and verified in the
proxy, so downloads pass without Basic auth while all other API requests stay
password-protected.

- app/api/auth/download-token/route.ts: issue tokens for files inside allowed
  roots (symlinks re-checked); Basic auth guarded by the proxy.
- proxy.ts: download requests with a valid token skip the Basic check; invalid
  tokens get an explicit 403.
- components/FileExplorer.tsx: prefetch the token on hover and write it into
  the download href before Pake's capture-phase click interception; attach a
  readable name via the download attribute only for extensions Pake hijacks.
- lib/download-auth.ts + lib/pake-download-extensions.ts with unit tests.

Verified with curl against a dev server: token download returns 200 with full
bytes, missing/invalid/expired tokens are rejected, basic-auth downloads
regress clean.
@agegr

agegr commented Aug 25, 2026

Copy link
Copy Markdown
Owner

建议从 Pake 侧解决这个问题。根因是 Pake 拦截同源下载链接后,使用独立 HTTP 客户端重新请求文件,导致 WebView 已保存的 Basic Auth 凭据丢失。

更简单的方案是:Pake 对同源下载保留 WebView/浏览器原生下载流程,只对跨域链接使用原生下载器;或者提供一个明确的 bypass 属性,让 Pi Web 的下载链接跳过 Pake 拦截。这样浏览器会自然携带 Basic Auth,文件名也可继续由 Content-Disposition 处理。

当前方案为一个 Pake 特有行为在 Pi Web 中增加了约 600 行令牌、路径校验和扩展名同步代码,同时仍遗漏 FileViewer 下载入口,并存在路径重复解码和 hover 竞态。建议先推动 Pake 修复;只有确认无法从 Pake 侧解决且有足够用户需求时,再考虑服务端签名下载方案。

@agegr agegr closed this Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants